import logging
import os
import tarfile
import tempfile
import zipfile
from io import BytesIO
import json
from django.core.exceptions import PermissionDenied
from django.core.files.storage import default_storage
from django.core.paginator import Paginator, EmptyPage
from django.views.decorators.http import require_http_methods
import requests

from apiutils.response import ApiResponse
from reimbursement import common
from django.http import JsonResponse, HttpResponse, FileResponse
from openpyxl import load_workbook
from Tools.excelTools import execlTools
from Tools.numTools import numtools
from datetime import datetime

from django.contrib.auth.decorators import login_required
from Tools.logtools.logtools import logfile_hander, console_handler
logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)
logger.addHandler(logfile_hander())
logger.addHandler(console_handler())

'''加载yaml文件'''
#^_^ 2024-12-18-shp 修改yaml
import  ruamel.yaml
def demo_config():
    """
    加载全局配置文件
    :return:
    """
    # 创建YAML对象，并指定加载类型为 'rt'（即 Round-Trip，保持原始格式）
    yaml = ruamel.yaml.YAML(typ='rt')
    #os.chdir(os.path.dirname(os.path.abspath(__file__)))
    with open("../Tools/config/config.yaml", "r") as smallfile:
        config = yaml.load(smallfile)  # pprint(config)
    return config
cfg = demo_config()



#shp 2025-04-10获取差旅xlsx
@login_required
@require_http_methods(["GET"])
def getrbmexecl(request):

    """
    2025-02-10 shp 获取差旅报销单的execl
    :return:
    """
    try:
        if 'ctid' not in request.GET:

            return ApiResponse.error(message=f"缺少必要参数ctid")

        try:
            ctid = int(request.GET['ctid'])
        except ValueError:
            return ApiResponse.error(message=f"ctid必须有效")

        #暂时数据直接从数据库里拿取
        rows_ct2,row_ct1 =  common.get_rmb_info(ctid)

        rows_ct2,row_ct1 = common.get_reibursement_info(row_ct1,rows_ct2,ctid)

        # 打开
        # 加载现有的Excel工作簿
        workbook = load_workbook('../static/doctemplate/差旅报销单.xlsx')

        # 写入数据
        sheet = workbook['Sheet1']

        # 姓名
        currentname = sheet['A3'].value
        sheet['A3'] = currentname + ':' + row_ct1['nick_name']

        # 职务
        currentname = sheet['B3'].value
        sheet['B3'] = currentname + ':' + row_ct1['positionname']

        # 出差事由
        sheet['E3'] =  str(row_ct1['ReforBu_trip'])

        # 机，车，船票
        if row_ct1['ticket'] == None:
            sheet['G5'] = '\\'
        else:
            sheet['G5'] = row_ct1['ticket']

        #住宿票
        if row_ct1['quarterage'] == None:
            sheet['G6'] = '\\'
        else:
            sheet['G6'] = row_ct1['quarterage']

        #市内交通费单据
        if row_ct1['cityticket'] == None:
            sheet['G7'] = '\\'
        else:
            sheet['G7'] = row_ct1['cityticket']

        #出差补贴
        if row_ct1['foodallowance'] == None:
            sheet['G8'] = '\\'
        else:
            sheet['G8'] = row_ct1['foodallowance']


        #其他1
        if row_ct1['Other_1'] != None:
            sheet['F9'] = row_ct1['Othername_1']
            sheet['G9'] = row_ct1['Other_1']
        #其他2
        if row_ct1['Other_2'] != None:
            sheet['F10'] = row_ct1['Othername_2']
            sheet['G10'] = row_ct1['Other_2']
        #其他3
        if row_ct1['Other_3'] != None:
            sheet['F11'] = row_ct1['Othername_3']
            sheet['G11'] = row_ct1['Other_3']

        #合计
        if row_ct1['Total'] == None:
            sheet['G12'] = '\\'
        else:
            sheet['G12'] = row_ct1['Total']
            currentname = sheet['A12'].value
            sheet['A12'] = currentname  + str(numtools.num_to_cn_upper(float( row_ct1['Total']) ))


        index = 5
        for row in rows_ct2:

            #在途天数
            if row['Days_in_transit'] == None:
                sheet['C'+str(index)] = '\\'
            else:
                sheet['C'+str(index)] = row['Days_in_transit']
            #住勤天数
            if row['Stayperiod'] == None:
                sheet['D'+str(index)] = '\\'
            else:
                sheet['D'+str(index)] = row['Stayperiod']

            end_date = row['End_time']
            start_date = row['Start_time']

            #起止日期
            sheet['A'+str(index)] =str(start_date.month) + '_' + str(start_date.day) + '---' + str(end_date.month) + '_' + str(end_date.day)
            #起止地点
            sheet['B'+str(index)] = str(row['Start_location']) + '---' + str(row['End_location'])
            index += 1


        #获取execlname
        now = datetime.now()
        month = now.month
        day = now.day
        execlname = f'{month}_{day}'


        output = BytesIO()
        workbook.save(output)

        # 重置 BytesIO 对象的文件指针到开头
        output.seek(0)

        reponse = FileResponse(output, as_attachment=True, filename= f"差旅报销单_{execlname}.xlsx",content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
        #reponse['Content_type'] = HttpResponse(content_type='application/octet-stream')
        #reponse['Content-Type'] = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
        #reponse['Content-Disposition'] = f'attachment; filename=差旅报销单({execlname}).xlsx'
        return reponse
    except Exception as e:
        logger.error(f"服务器错误: {str(e)}", exc_info=True)
        return ApiResponse(code=500, message="服务器内部错误").to_json()

#shp 2025-04-10获取差旅数据库中的表格
@login_required
def getTravelReimburse(request):
    """
    shp 2025-04-10获取差旅数据库中的表格
    :param request:
    :return:
    """
    try:
        # 获取分页参数并转换为整数
        page = int(request.GET.get('page', 1))
        limit = int(request.GET.get('limit', 10))
        # 验证参数有效性
        if page < 1 or limit < 1:
            logger.error(f"页码和分页大小小于0:", exc_info=True)
            return ApiResponse(code=300, message="页码和分页大小必须大于0").to_json()
        data=list(common.select_TravelReimburse(1))
        # 创建分页器
        paginator = Paginator(data, limit)
        try:
            current_page = paginator.page(page)
        except EmptyPage:
            # 如果页码超出范围，返回最后一页
            current_page = paginator.page(paginator.num_pages)
        return ApiResponse(
            code = 0,
            data = current_page.object_list,
            count = current_page.paginator.count,
            message = "执行TravelReimburse查找成功"
        ).to_json()
    except PermissionDenied as e:
        logger.warning(f"权限拒绝: {request.user} - {str(e)}")
        return ApiResponse(code=403, message=str(e)).to_json()
    except Exception as e:
        logger.error(f"服务器错误: {str(e)}", exc_info=True)
        return ApiResponse(code=500, message="服务器内部错误").to_json()

@login_required
@require_http_methods(["POST"])
def addTravelReimburse(request):
    """
    新增表格TravelReimburse
    :param request:
    :return:
    """
    # 阶段1：请求解析与基础验证
    try:
        json_data = json.loads(request.body)
    except json.JSONDecodeError:
        return ApiResponse.error(message="请求数据格式错误")

    basic_info = json_data.get('basic_info', {})
    if not basic_info.get('document_name'):
        return ApiResponse.error(message="单据名称不能为空")

    # 阶段2：业务数据处理
    try:
        # 数据预处理（可提取为独立方法）
        processed_data = {
            'basic_info': basic_info,
            'cost_details': json_data.get('cost_details', {}),
            'additional_costs': json_data.get('additional_costs', []),
            'trip_schedules': json_data.get('trip_schedules', [])
        }
        logger.info(f"正在执行表TravelReimburse插入")
        # 阶段3：数据持久化
        common.save_travel_data(
            user=request.user,
            form_data=processed_data
        )
        logger.info(f"表TravelReimburse插入成功")
        return ApiResponse.success(data=0)

    except KeyError as e:
        logger.warning(f"缺少必要字段: {str(e)}")
        return ApiResponse.error(message=f"缺少必要字段: {e}")
    except ValueError as e:
        logger.warning(f"数据类型错误: {str(e)}")
        return ApiResponse.error(message="金额格式不正确")
    except Exception as e:
        logger.error(f"报销单创建异常: {str(e)}", exc_info=True)
        return ApiResponse.error(message="系统处理异常，请检查数据格式")

@login_required
def deleteTravelReimburse(request):
    try:
        if request.method == 'POST':
            if not request.POST.get('id'):
                return ApiResponse.error(message="id字段不能为空")
            id = request.POST.get('id')
            common.delete_TravelReimburse(id)
            return ApiResponse(code = 0,message=f"删除TravelReimburse成功").to_json()
    except Exception as e:
        # 记录错误日志
        logger.error(f"删除报销单失败: {str(e)}")
        return ApiResponse.error(message=str(e))
    except KeyError as e:
        logger.error(f"缺少必要字段: {str(e)}")
        return ApiResponse.error(message=f"缺少必要字段: {str(e)}")

def getmerchantinfo(request):
    """
    2025-05-08 从ocr获取支付信息
    流程：接收压缩包 → 解压 → 图片识别 → 数据入库
    :param requset:
    :return:
    """
    if request.method != 'POST':
        logger.error(f"请求方式不正确")
        return ApiResponse.error(message =f'请求方式不正确')
    with tempfile.TemporaryDirectory() as tmpdir:
        try:
            # 1. 接收上传文件
            uploaded_file = request.FILES.get('file')
            if not uploaded_file:
                logger.error(f"没找到上传文件")
                return ApiResponse.error(message = f'没找到上传文件')

            # 保存临时压缩包
            tmp_path = os.path.join(tmpdir, uploaded_file.name)
            with  open(tmp_path, 'wb+') as destination:
                for chunk in uploaded_file.chunks():
                    destination.write(chunk)

                    # 2. 解压文件
                    extracted_files = []
                    if zipfile.is_zipfile(tmp_path):
                        with zipfile.ZipFile(tmp_path, 'r') as zip_ref:
                            zip_ref.extractall(tmpdir)
                            extracted_files = zip_ref.namelist()
                    elif tarfile.is_tarfile(tmp_path):
                        with tarfile.open(tmp_path, 'r:gz') as tar_ref:
                            tar_ref.extractall(tmpdir)
                            extracted_files = tar_ref.getnames()
                    else:
                        logger.error(f"不支持解压此文件,文件名{tmp_path}")
                        return ApiResponse.error(message = f'不支持解压此文件')

                    # 3. 处理图片文件
                    success_count = 0
                    for filename in extracted_files:
                        file_path = os.path.join(tmpdir, filename)

                        # 验证图片格式
                        if not os.path.isfile(file_path):
                            continue
                        if os.path.splitext(filename)[-1].lower() not in ['.jpg', '.png']:
                            logger.warning(f'Skipped non-image file: {filename}')
                            continue

                    #4. 调用OCR接口
                        try:
                            with open(file_path, 'rb') as img_file:
                                response = requests.post(
                                    f'http://{cfg["doumo"]["ocr"]["ip"]}:{cfg["doumo"]["ocr"]["port"]}/api/v2/ocr',
                                    files={'file': img_file},
                                    timeout=30
                                )
                            response.raise_for_status()
                            if response.json().get('success', ''):
                                ocr_data = response.json().get('data', '')
                                logger.info(f"ocr识别{img_file}成功：{ocr_data}")
                                common.enhanced_ocr_cleaner(ocr_data,request.user)

                            else:
                                logger.error(f'OCR 识别失败 {filename}: {str(e)}')
                        except Exception as e:
                            logger.error(f'OCR processing failed for {filename}: {str(e)}')
                            #return ApiResponse.error(message = f'系统内部错误')
                            continue

            return JsonResponse({
                'code': 200,
                'success': True,
                'processed': len(extracted_files),
                'saved': success_count
            })

        except Exception as e:
            logger.exception('Processing failed')
            return JsonResponse({'code': 500, 'error': str(e)}, status=500)

